%%% Copyright 2010 Google Inc.
%%%
%%% Licensed under the Apache License, Version 2.0 (the "License");
%%% you may not use this file except in compliance with the License.
%%% You may obtain a copy of the License at
%%%
%%%     http://www.apache.org/licenses/LICENSE-2.0
%%%
%%% Unless required by applicable law or agreed to in writing, software
%%% distributed under the License is distributed on an "AS IS" BASIS,
%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%%% See the License for the specific language governing permissions and
%%% limitations under the License.

%%% @copyright 2010 Google Inc. All Rights Reserved.
%%% @author <jacoby@google.com> Jacoby Thwaites [http://sites/madcloud]
%%% @version {@version}
%%% @doc Tests for view_db module.
%%%
%%% Note that this deletes all mnesia data on the test node.
%%%
-module(view_db_tests).

%% Include eunit and txm headers
-include_lib("eunit/include/eunit.hrl").
-include("txm.hrl").
-include("view.hrl").

-export([mock_view_fsm/0]).

%% Test group with setup and teardown of Mnesia.
big_test_() ->

  % Tests depend on preceding tests.
  {inorder,

   % This tuple specifies a setup fun(), teardown fun() and list of test fun().
   {setup,
    % Setup creates new schema and tables.
    fun() ->
         mnesia:start(),
         view_db:create_tables(),
         view_db:start_link(),
         V1 = mock_view_fsm(),
         V2 = mock_view_fsm(),
         {V1, V2}
    end,

    % Cleanup deletes schema.
    fun(_) ->
         view_db:stop(),
         mnesia:stop()
    end,

    % Test functions grouped by area.
    fun(Views) -> [
      basic_tests(Views),
      multi_tests(Views),
      kill_tests(Views)]
    end
    }}.


%% Returns a list of functions that test view_db functionality.
basic_tests({V1, _V2}) ->
  [
    % Checks activation of first fake view
    fun() ->
         ?assertEqual(ok,
                      view_db:activate(
                        V1,
                        {folder, {0, 1, 2}},
                        "Cookie 1",
                        [{op, {2, 0, 0}}, {op, {2, 0, 1}}, {op, {2, 0, 2}}]))
    end,

    % Check view_fsm retrieval of each op ref with matching cookie.
    fun() ->
         lists:foreach(
           fun(OpRef) ->
                [ViewFsm] = view_db:matching_views({folder, {0, 1, 2}},
                                                   "Cookie 1",
                                                   OpRef),
                ?assertEqual(V1, ViewFsm)
           end, [{op, {2, 0, 0}}, {op, {2, 0, 1}}, {op, {2, 0, 2}}])
    end,

    % Check deactivation of view.
    fun() ->
         view_db:deactivate(V1),
         ?assertEqual([], view_db:matching_views({folder, {0, 1, 2}},
                                                 "Cookie 1",
                                                 {op, {2, 0, 0}}))
    end
    ].

%% Returns a list of functions that test multiple view_fsm activations.
multi_tests({V1, V2}) ->
  [
    % Checks matching of two views on same composition/ops but with
    % different cookies.
    fun() ->
         view_db:activate(V1,
                          {folder, {0, 1, 2}},
                          "Cookie 1",
                          [{op, {2, 0, 0}}, {op, {2, 0, 1}}, {op, {2, 0, 2}}]),
         view_db:activate(V2,
                          {folder, {0, 1, 2}},
                          "Cookie 2",
                          [{op, {2, 0, 0}}, {op, {2, 0, 1}}, {op, {2, 0, 2}}]),

         [View1] = view_db:matching_views({folder, {0, 1, 2}},
                                      "Cookie 1",
                                      {op, {2, 0, 1}}),

         [View2] = view_db:matching_views({folder, {0, 1, 2}},
                                      "Cookie 2",
                                      {op, {2, 0, 1}}),
         ?assertEqual(V1, View1),
         ?assertEqual(V2, View2)
    end,

    %% Checks matching of '$any_cookie' view.
    fun() ->
         view_db:deactivate(V1),
         view_db:activate(V1,
                          {folder, {0, 1, 50}},
                          '$any_cookie',
                          [{op, {2, 0, 0}}, {op, {2, 0, 1}}, {op, {2, 0, 2}}]),
         [View1] = view_db:matching_views({folder, {0, 1, 50}},
                                      "Cookie 1",
                                      {op, {2, 0, 2}}),
         ?assertEqual(V1, View1)
    end,

    %% Checks matching of multiple views.
    fun() ->
         view_db:activate(V1,
                          {folder, {0, 1, 2}},
                          '$any_cookie',
                          [{op, {2, 0, 0}}, {op, {2, 0, 1}}, {op, {2, 0, 2}}]),
         view_db:activate(V2,
                          {folder, {0, 1, 2}},
                          "Cookie 2",
                          [{op, {2, 0, 0}}, {op, {2, 0, 1}}, {op, {2, 0, 2}}]),
         Views = view_db:matching_views({folder, {0, 1, 2}},
                                        "Cookie 2",
                                        {op, {2, 0, 1}}),
         ?assertEqual(2, length(Views)),
         ?assertEqual([], lists:subtract(Views, [V1, V2])),

         view_db:deactivate(V1),
         view_db:deactivate(V2)
     end
    ].

%% Returns a list of functions that test deactivation upon view_fsm termination.
kill_tests({V1, _V2}) ->
  [
    % Checks view_db is cleaned up after view_fsm process termination.
    fun() ->
         view_db:activate(V1,
                          {folder, {0, 1, 2}},
                          "Cookie 1",
                          [{op, {2, 0, 0}}, {op, {2, 0, 1}}, {op, {2, 0, 2}}]),

         % Check the view is active.
         [View1] = view_db:matching_views({folder, {0, 1, 2}},
                                          "Cookie 1",
                                          {op, {2, 0, 0}}),
         ?assertEqual(V1, View1),

         % Stop the mock view_fsm and sleep for a second.
         V1 ! stop,
         timer:sleep(1000),

         % By now the view_db should have got the exit signal and
         % deactivated the view_fsm.
         ?assertEqual([], view_db:matching_views({folder, {0, 1, 2}},
                                                 "Cookie 1",
                                                 {op, {2, 0, 0}}))
    end
    ].

%% Creates a mock view fsm process that can be stopped.
mock_view_fsm() ->
  spawn_link(fun loop/0).

loop() ->
  receive
    stop ->
      ok;
    _ -> loop()
  end.
